﻿using ICSharpCode.AvalonEdit.Highlighting;
using MahApps.Metro.Controls;
using NFox.Pycad;
using NFox.Pyeditor.ViewModel;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
using Xceed.Wpf.AvalonDock.Layout;

namespace NFox.Pyeditor.Runtime
{

   

    /// <summary>
    /// UserControl1.xaml 的交互逻辑
    /// </summary>
    public partial class PyIdeWindow : MetroWindow
    {

        ModuleViewModel _root;

        public PyIdeWindow()
        {

            InitializeComponent();

            Title = $"Pycad编辑器 v{Application.Version}";
            locvarlistwin.Hide();

            var menu = prjtree.Resources["TreeViewItemContextMenu"] as ContextMenu;
            var templates = menu.Items[0] as MenuItem;
            Application.Engine.Execute("import pyedit.templates");
            foreach (string key in Application.Engine.Execute("pyedit.templates.__all__"))
            {
                var template = Application.Engine.Execute($"pyedit.templates.{key}");
                var item = 
                    new MenuItem
                    {
                        Header = $"{template.name}({key})",
                        Command = new RoutedUICommand(),
                        CommandParameter = template.text,
                        CommandTarget = prjtree
                    };
                CommandBindings.Add(
                    new CommandBinding(item.Command, newmodule, cannewmodule));
                templates.Items.Add(item);
            }

            _root = new ModuleViewModel(Application.Engine.Projects);
            _root.IsExpanded = true;
            prjtree.ItemsSource = new ObservableCollection<ModuleViewModel> { _root };
            prjtree.Focus();

            var updateTimer = new DispatcherTimer();
            updateTimer.Interval = TimeSpan.FromSeconds(3);
            updateTimer.Tick += update;
            updateTimer.Start();
            Restart();
        }

        private void Restart()
        {
            Application.Engine.Execute("ed()");
            Application.Engine.Execute("stdio").redirect(new ConsoleStream(console));
            _ds = new Debugger(this);
            SetStyle();
        }

        protected override void OnClosed(EventArgs e)
        {
            base.OnClosed(e);
            _ds.Dispose();
            Application.Engine.Execute("stdio").redirect(null);
        }

        public void SetStyle()
        {
            Application.Engine.Execute("import pyedit.styles");
            dynamic cstyle = Application.Engine.Execute("pyedit.styles.cstyle");
            var hd = HighlightingManager.Instance.GetDefinition("Python");
            foreach (var hc in hd.NamedHighlightingColors)
                hc.Foreground =
                    new SimpleHighlightingBrush(
                        ColorConverter.ConvertFromString(
                            cstyle.HighlightingColors[hc.Name]));

            foreach (var lst in cstyle.Colors.items())
                Application.Engine.Execute("setattr")(
                    cstyle, lst[0],
                    new SolidColorBrush(
                        ColorConverter.ConvertFromString(lst[1])));

            CodeEditor.Cstyle = cstyle;
            console.FontFamily = new FontFamily(cstyle.Font[0]);
            console.FontSize = cstyle.Font[1];
        }

        private void update(object sender, EventArgs e)
        {
            var doc = (LayoutDocument)docs.SelectedContent;
            var code = (CodeEditor)doc?.Content;
            if (code != null && code.MarkerService.Update())
            {
                //Dispatcher.Invoke(new Action(code.MarkerService.UpdateFoldings));
            }
        }

        #region Debug

        Debugger _ds;


        private void canstart(object sender, CanExecuteRoutedEventArgs e)
        {
            if (_ds != null)
            {
                e.CanExecute = 
                    !_ds.Enabled || 
                    _ds.CommandType == Debugger.CommandTypes.Wait;
                if(btnStart != null)
                    btnStart.ToolTip = _ds.Enabled ? "继续" : "启动";
            }
        }

        private void candebug(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = 
                _ds != null &&
                _ds.Enabled && 
                _ds.CommandType == Debugger.CommandTypes.Wait;
        }

        private void debugstart(object sender, ExecutedRoutedEventArgs e)
        {
            if (_ds.Enabled)
            {
                _ds.Continue();
            }
            else
            {
                var doc = docs.SelectedContent as LayoutDocument;
                var code = doc?.Content as CodeEditor;
                if (code != null)
                {
                    var tdoc = code.Document;
                    var line = tdoc.GetLineByOffset(code.CaretOffset);
                    var s = tdoc.GetText(line);
                    var m = Regex.Match(s, "def\\s*(.*?)\\(.*?\\)");
                    if(m.Success) _ds.Start(m.Groups[1].Value);
                }
            }
        }


        private void debugend(object sender, ExecutedRoutedEventArgs e)
        {
            _ds.Quit();
        }

        private void debugsetp(object sender, ExecutedRoutedEventArgs e)
        {
            _ds.Setp();
        }

        private void debugnext(object sender, ExecutedRoutedEventArgs e)
        {
            _ds.Next();
        }

        private void debugreturn(object sender, ExecutedRoutedEventArgs e)
        {
            _ds.Return();
        }

        #endregion

        #region Console

        private void commandStartting(object sender, CommandEventArgs e)
        {
            string cmd = e.Command;
            var patts =
                new Dictionary<string, string>
                {
                    { @"^\?(.*?)$", "help('$1')"},
                    { @"^\@(.*?)\((.*?)\)$", "pye.debug('$1',$2)" } ,
                    { @"^\@(.*?)$", "pye.debug('$1')" },
                    { @"^\$(.*?)$", "import pdb;pdb.set_trace();$1"},
                };
            foreach (var kv in patts)
            {
                if (Regex.IsMatch(cmd, kv.Key))
                {
                    cmd = Regex.Replace(cmd, kv.Key, kv.Value);
                    break;
                }
            }
            e.Result = Application.Engine.Execute(cmd);
        }

        private void commandEnded(object sender, CommandEventArgs e)
        {
            if (e.Result != null)
            {
                var res = e.Result;
                if (res is string)
                    res = $"'{res}'";
                Application.Engine.Print(res);
            }
        }

        #endregion

        #region Save

        private void canbuild(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = true;
        }

        private void build(object sender, ExecutedRoutedEventArgs e)
        {
            SaveAll();
            Application.GetPlugin("NFox.Pycad.Core")?.Start();
            Restart();
        }

        private void canexec(object sender, CanExecuteRoutedEventArgs e)
        {
            if (docs != null && docs.Children != null)
            {
                e.CanExecute = docs.Children.Count > 0;
            }
        }

        private void SaveFile(LayoutDocument doc)
        {
            var code = doc.Content as CodeEditor;
            Module m = code.Module;
            using (var sw = new StreamWriter(m.File.FullName, false, Encoding.UTF8))
                sw.Write(code.Text);
            code.Modified = false;
        }

        private void SaveAll()
        {
            foreach (LayoutDocument doc in docs.Children)
                SaveFile(doc);
        }

        private void save(object sender, ExecutedRoutedEventArgs e)
        {
            if (e.Parameter.ToString() == "Single")
            {
                var doc = docs.SelectedContent as LayoutDocument;
                SaveFile(doc);
            }
            else
            {
                SaveAll();
            }
        }

        #endregion

        #region Redo/Undo

        public CodeEditor GetSelCode()
        {
            dynamic doc = docs?.SelectedContent;
            return doc?.Content;
        }

        private void redo(object sender, ExecutedRoutedEventArgs e)
        {
            GetSelCode()?.Redo();
        }

        private void undo(object sender, ExecutedRoutedEventArgs e)
        {
            GetSelCode()?.Undo();
        }

        #endregion

        #region treeview

        private Module GetSelModule()
        {
            var module = prjtree.SelectedValue as ModuleViewModel;
            return module?.Model;
        }

        private void canrelease(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = true;
        }

        private void release(object sender, ExecutedRoutedEventArgs e)
        {
            Module m = GetSelModule();
            Application.Engine.Release(m.Name, new List<Module> { m });
        }

        Module _edit;
        bool _cut;

        private void canedit(object sender, CanExecuteRoutedEventArgs e)
        {
            var m = GetSelModule();
            e.CanExecute = !m.IsRoot;
        }

        private void cut(object sender, ExecutedRoutedEventArgs e)
        {
            _edit = GetSelModule();
            _cut = true;
        }

        private void copy(object sender, ExecutedRoutedEventArgs e)
        {
            _edit = GetSelModule();
            _cut = false;
        }

        private void canpaste(object sender, CanExecuteRoutedEventArgs e)
        {
            var p = GetSelModule();
            e.CanExecute =_edit != null && p.IsPackage && !_edit.IsSubOf(p) && !p.IsPartOf(_edit);
        }

        private void paste(object sender, ExecutedRoutedEventArgs e)
        {
            var p = GetSelModule() as Package;
            _edit.CopyTo(p);
            if (_cut)
            {
                _edit.Remove();
                _cut = false;
            }
            _edit = null;
        }

        private void rename(object sender, ExecutedRoutedEventArgs e)
        {
            var m = GetSelModule();
            InputBoxValidation validation = s => m.Parent.Validate(s, m.IsPackage);
            var name = m.Name;
            if (InputBox.Show(GetPos(), "重命名", "名称:", ref name, m.IsPackage ? 0 : -3, validation))
            {
                m.Rename(name);
            }
        }

        private void delete(object sender, ExecutedRoutedEventArgs e)
        {
            GetSelModule().Remove();
        }

        private void cannewpackage(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = true;
        }

        private void cannewmodule(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = GetSelModule() != Application.Engine.Projects;
        }

        private System.Drawing.Point GetPos()
        {
            var pos = PointToScreen(Mouse.GetPosition(this));
            return new System.Drawing.Point((int)pos.X, (int)pos.Y);
        }

        private void newpackage(object sender, ExecutedRoutedEventArgs e)
        {
            var package = GetSelModule() as Package;
            InputBoxValidation validation = s => package.Validate(s, true);
            string name = "untitled";
            
            if (InputBox.Show(GetPos(), "输入包名", "名称:", ref name, 0, validation))
            {
                var prj = package.CreateSubPackage(name) as Project;
                if (prj != null)
                {
                    foreach (var spec in SpecPackage.Names)
                        prj.CreateSubPackage(spec);
                    foreach (var vm in _root.Items)
                    {
                        if (vm.Name == name)
                        {
                            vm.IsExpanded = true;
                            break;
                        }
                    }
                }
            }
        }

        private void newmodule(object sender, ExecutedRoutedEventArgs e)
        {
            var package = GetSelModule() as Package;
            InputBoxValidation validation = s => package.Validate(s, false);
            string name = "untitled.py";
            if (InputBox.Show(GetPos(), "输入模块名", "名称:", ref name, -3, validation))
            {
                var content = string.Format(e.Parameter.ToString(), name.Substring(0, name.Length - 3));
                package.CreateModule(name, content);
            }
        }


        public List<IDocument> OpenedFiles { get; }
            = new List<IDocument>();

        private void mouseDoubleClick(object sender, RoutedEventArgs e)
        {
            NewDocument(GetSelModule());
            e.Handled = true;
        }

        public void NewDocument(Module m)
        {

            if (m == null || m.IsRoot || m is SpecPackage && m.Name == "data")
                return;

            if (m.Document == null || !OpenedFiles.Contains(m.Document))
            {
                var doc = new LayoutDocument();
                doc.Description = m.File.FullName;
                doc.Title = m.Name;
                var code = new CodeEditor(m);
                doc.Content = code;
                docs.Children.Add(doc);
                m.Document = new Document(doc);
                OpenedFiles.Add(m.Document);
            }
            else
            {
                if (m.Document.Content.Parent == null)
                {
                    docs.Children.Add(m.Document.Content);
                }
            }

            docs.SelectedContentIndex = docs.IndexOfChild(m.Document.Content);
            if (m.IsPackage)
                m.Tag.IsExpanded = true;

        }

        public CodeEditor ScrollTo(BreakPoint bt)
        {

            var code = GetSelCode();
            code.MarkerService.ClearDebugMarker();
            bool find = code.Module.Path == bt.FileName;
            if (!find)
            {
                foreach (var idoc in OpenedFiles)
                {
                    var doc = idoc.Content as LayoutDocument;
                    code = doc.Content as CodeEditor;
                    if (code.Module.Path == bt.FileName)
                    {
                        if (doc.Parent == null)
                            docs.Children.Add(doc);
                        docs.SelectedContentIndex = docs.IndexOfChild(doc);
                        find = true;
                    }
                }
            }

            if (find)
            {
                code.ScrollToLine(bt.Line);
            }
            else
            {
                Module m =
                    Application.Engine.Projects.FindModuleByPath(bt.FileName) ??
                    Module.FromFile(bt.FileName);
                NewDocument(m);
                double dlh = code.TextArea.TextView.DefaultLineHeight;
                int n = (int)Math.Floor(code.ActualHeight / dlh);
                code = GetSelCode();
                if (bt.Line > n)
                    code.ScrollToVerticalOffset(dlh * (bt.Line - n / 2));
            }
            code.MarkerService.AddDebugMarkerAt(bt);
            return code;

        }

        bool _rightdown;

        private void previewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            var treeViewItem = VisualUpwardSearch<TreeViewItem>(e.OriginalSource as DependencyObject) as TreeViewItem;
            if (treeViewItem != null)
            {
                _rightdown = true;
                treeViewItem.Focus();
                e.Handled = true;
            }
        }

        private DependencyObject VisualUpwardSearch<T>(DependencyObject source)
        {
            while (source != null && source.GetType() != typeof(T))
                source = VisualTreeHelper.GetParent(source);
            return source;
        }

        private void selectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        {
            if (_rightdown)
            {
                _rightdown = false;
            }
            else
            {
                NewDocument(GetSelModule());
            }

        }

        #endregion

    }

    public class VisibilityConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return (bool)value ? Visibility.Visible : Visibility.Collapsed;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

    public class IndentConverter : IValueConverter
    {
        public double Indent { get; set; }

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var item = value as TreeViewItem;
            if (item == null)
                return new Thickness(0);
            var m = item.Header as ModuleViewModel;
            return new Thickness(Indent * m.Depth, 0, 0, 0);
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }





}
